/*
 * Copyright (C) 2009 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package cn.sinjet.communication.bluetooth;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.UUID;

import android.annotation.SuppressLint;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothServerSocket;
import android.bluetooth.BluetoothSocket;
import android.util.Log;

/**
 * This class does all the work for setting up and managing Bluetooth
 * connections with other devices. It has a thread that listens for incoming
 * connections, a thread for connecting with a device, and a thread for
 * performing data transmissions when connected.
 */
public class SppBluetoothChatService {
	// Debugging
	private static final String TAG = "SPP_SERVICE";
	private static final boolean D = true;

	// Name for the SDP record when creating server socket
	private static final String NAME_SECURE = "BluetoothChatSecure";
	private static final String NAME_INSECURE = "BluetoothChatInsecure";

	// Unique UUID for this application
	private static final UUID MY_UUID_SECURE = UUID
			.fromString("00001101-0000-1000-8000-00805F9B34FB");
	private static final UUID MY_UUID_INSECURE = UUID
			.fromString("00001101-0000-1000-8000-00805F9B34FB");
	// UUID.fromString("8ce255c0-200a-11e0-ac64-0800200c9a66");

	// Member fields
	private final BluetoothAdapter mAdapter;
	//private final Handler mHandler;
	/*
	 * private AcceptThread mSecureAcceptThread; private AcceptThread
	 * mInsecureAcceptThread;
	 */
	private ConnectThread mConnectThread;
	private ConnectedThread mConnectedThread;
	private int mConnectState = BTState.DISCONNECTED;
	private OnChatServiceListener mOnChatServiceListener = null;
	/**
	 * Constructor. Prepares a new BluetoothChat session.
	 * 
	 * @param context
	 *            The UI Activity Context
	 */
	public SppBluetoothChatService() {
		mAdapter = BluetoothAdapter.getDefaultAdapter();
		//mConnectState = STATE_NONE;
		//mHandler = handler;
	}

	interface OnChatServiceListener{
		void onStateChange(int state,int flag);
		void onReceived(byte[] buf,int len);
	}
	
		
	public void setOnChatServiceListener(OnChatServiceListener onChatServiceListener){
		mOnChatServiceListener = onChatServiceListener;
	}
	
	
	/**
	 * Set the current state of the chat connection
	 * 
	 * @param state
	 *            An integer defining the current connection state
	 */
	private synchronized void onStateChange(int state,int flag) {
//		if (D)
		Log.d(TAG, "onStateChange " + mConnectState + " -> " + state);
		mConnectState = state;
		if(mOnChatServiceListener != null)
			mOnChatServiceListener.onStateChange(state,flag);
	
	}

	/**
	 * Return the current connection state.
	 */
	public synchronized int getState() {
		return mConnectState;
	}

	/**
	 * Start the chat service. Specifically start AcceptThread to begin a
	 * session in listening (server) mode. Called by the Activity onResume()
	 */
	public synchronized void cancelConnectThread() {
		// Cancel any thread attempting to make a connection
		if (mConnectThread != null) {
			mConnectThread.cancel();
			mConnectThread = null;
		}

		// Cancel any thread currently running a connection
		if (mConnectedThread != null) {
			mConnectedThread.cancel();
			mConnectedThread = null;
		}
	}

	/**
	 * Start the ConnectThread to initiate a connection to a remote device.
	 * 
	 * @param device
	 *            The BluetoothDevice to connect
	 * @param secure
	 *            Socket Security type - Secure (true) , Insecure (false)
	 * @param secure 
	 */
	public synchronized void connect(BluetoothDevice device, boolean isSearchResult, boolean secure) {
		// Cancel any thread attempting to make a connection
		if (mConnectState == BTState.CONNECTING) {
			if (mConnectThread != null) {
				mConnectThread.cancel();
				mConnectThread = null;
			}
		}

		// Cancel any thread currently running a connection
		if (mConnectedThread != null) {
			mConnectedThread.cancel();
			mConnectedThread = null;
		}

		// Start the thread to connect with the given device
		mConnectThread = new ConnectThread(device,isSearchResult,secure);
		mConnectThread.start();
		onStateChange(BTState.CONNECTING,0);
	}

	/**
	 * Start the ConnectedThread to begin managing a Bluetooth connection
	 * 
	 * @param socket
	 *            The BluetoothSocket on which the connection was made
	 * @param device
	 *            The BluetoothDevice that has been connected
	 */
	public synchronized void connected(BluetoothSocket socket,
			BluetoothDevice device, final String socketType) {
		if (D)
			Log.d(TAG, "connected, Socket Type:" + socketType);

		// Cancel the thread that completed the connection
		if (mConnectThread != null) {
			mConnectThread.cancel();
			mConnectThread = null;
		}

		// Cancel any thread currently running a connection
		if (mConnectedThread != null) {
			mConnectedThread.cancel();
			mConnectedThread = null;
		}

		// Cancel the accept thread because we only want to connect to one
		// device
		/*
		 * if (mSecureAcceptThread != null) { mSecureAcceptThread.cancel();
		 * mSecureAcceptThread = null; } if (mInsecureAcceptThread != null) {
		 * mInsecureAcceptThread.cancel(); mInsecureAcceptThread = null; }
		 */

		// Start the thread to manage the connection and perform transmissions
		mConnectedThread = new ConnectedThread(this,socket, socketType);
		mConnectedThread.start();

		// Send the name of the connected device back to the UI Activity
		/*
		 * Message msg =
		 * mHandler.obtainMessage(BluetoothChat.MESSAGE_DEVICE_NAME); Bundle
		 * bundle = new Bundle(); bundle.putString(BluetoothChat.DEVICE_NAME,
		 * device.getName()); msg.setData(bundle); mHandler.sendMessage(msg);
		 */

		onStateChange(BTState.CONNECTED,0);
	}

	/**
	 * Stop all threads
	 */
	public synchronized void stop() {
		Log.d(TAG, "stop");

		if (mConnectThread != null) {
			mConnectThread.cancel();
			mConnectThread = null;
		}

		if (mConnectedThread != null) {
			mConnectedThread.cancel();
			mConnectedThread = null;
		}

		/*
		 * if (mSecureAcceptThread != null) { mSecureAcceptThread.cancel();
		 * mSecureAcceptThread = null; }
		 * 
		 * if (mInsecureAcceptThread != null) { mInsecureAcceptThread.cancel();
		 * mInsecureAcceptThread = null; }
		 */
//		Log.d(TAG,"connected thread stop make it disconnected");
	//	onStateChange(BTState.DISCONNECTED,0);
	}

	/**
	 * Write to the ConnectedThread in an unsynchronized manner
	 * 
	 * @param out
	 *            The bytes to write
	 * @see ConnectedThread#write(byte[])
	 */
	public void write(byte[] out) {
		// Create temporary object
		ConnectedThread r;
		// Synchronize a copy of the ConnectedThread
		synchronized (this) {
			if (mConnectState != BTState.CONNECTED)
				return;
			r = mConnectedThread;
		}
		// Perform the write unsynchronized
		r.write(out);
	}

	/**
	 * Indicate that the connection attempt failed and notify the UI Activity.
	 */
//	private void connectionFailed() {
//		// Send a failure message back to the Activity
//		SppBluetoothChatService.this.cancelConnectThread();
//	}

	/**
	 * Indicate that the connection was lost and notify the UI Activity.
	 */

	/**
	 * This thread runs while listening for incoming connections. It behaves
	 * like a server-side client. It runs until a connection is accepted (or
	 * until cancelled).
	 */
	private class AcceptThread extends Thread {
		// The local server socket
		private final BluetoothServerSocket mmServerSocket;
		private String mSocketType;

		@SuppressLint("NewApi")
		public AcceptThread(boolean secure) {
			BluetoothServerSocket tmp = null;
			mSocketType = secure ? "Secure" : "Insecure";

			// Create a new listening server socket
			try {
				if (secure) {
					tmp = mAdapter.listenUsingRfcommWithServiceRecord(
							NAME_SECURE, MY_UUID_SECURE);
				} else {
					tmp = mAdapter.listenUsingInsecureRfcommWithServiceRecord(
							NAME_INSECURE, MY_UUID_INSECURE);
				}
			} catch (IOException e) {
				Log.e(TAG, "Socket Type: " + mSocketType + "listen() failed", e);
			}
			mmServerSocket = tmp;
		}

		public void run() {
			if (D)
				Log.d(TAG, "Socket Type: " + mSocketType
						+ "BEGIN mAcceptThread" + this);
			setName("AcceptThread" + mSocketType);

			BluetoothSocket socket = null;

			// Listen to the server socket if we're not connected
			while (mConnectState != BTState.CONNECTED) {
				try {
					// This is a blocking call and will only return on a
					// successful connection or an exception
					socket = mmServerSocket.accept();
				} catch (IOException e) {
					Log.e(TAG, "Socket Type: " + mSocketType
							+ "accept() failed", e);
					break;
				}

				// If a connection was accepted
				if (socket != null) {
					synchronized (SppBluetoothChatService.this) {
						switch (mConnectState) {
						//case STATE_LISTEN:
						case BTState.CONNECTING:
							// Situation normal. Start the connected thread.
							connected(socket, socket.getRemoteDevice(),
									mSocketType);
							break;
						case BTState.DISCONNECTED:
							// Either not ready or already connected. Terminate
							// new socket.
							try {
								socket.close();
							} catch (IOException e) {
								Log.e(TAG, "Could not close unwanted socket", e);
							}
							break;
						}
					}
				}
			}
			if (D)
				Log.i(TAG, "END mAcceptThread, socket Type: " + mSocketType);

		}

		public void cancel() {
			if (D)
				Log.d(TAG, "Socket Type" + mSocketType + "cancel " + this);
			try {
				mmServerSocket.close();
			} catch (IOException e) {
				Log.e(TAG, "Socket Type" + mSocketType
						+ "close() of server failed", e);
			}
		}
	}

	/**
	 * This thread runs while attempting to make an outgoing connection with a
	 * device. It runs straight through; the connection either succeeds or
	 * fails.
	 */
	private class ConnectThread extends Thread {
		private final BluetoothSocket mmSocket;
		private final BluetoothDevice mmDevice;
		private String mSocketType;
        private boolean mIsSearchResult = false;
		@SuppressLint("NewApi")
		public ConnectThread(BluetoothDevice device, boolean isSearchResult, boolean secure) {
			mmDevice = device;
			BluetoothSocket socket = null;
			mSocketType = secure ? "Secure" : "Insecure";
            mIsSearchResult = isSearchResult;
			// Get a BluetoothSocket for a connection with the
			// given BluetoothDevice
			try {
				if (secure) {
					socket = device.createRfcommSocketToServiceRecord(MY_UUID_SECURE);
				} else {
					socket = device.createInsecureRfcommSocketToServiceRecord(MY_UUID_INSECURE);
				}
			} catch (IOException e) {
				Log.e(TAG, "Socket Type: " + mSocketType + "create() failed", e);
			}
			mmSocket = socket;
		}

		
		public void run() {
			Log.i(TAG, "BEGIN mConnectThread SocketType:" + mSocketType+" threadId:"+this.getId());
			setName("ConnectThread" + mSocketType);
			// Always cancel discovery because it will slow down a connection
			mAdapter.cancelDiscovery();
			// Make a connection to the BluetoothSocket
			try {
				// This is a blocking call and will only return on a
				// successful connection or an exception
				mmSocket.connect();
			} catch (IOException e) {
				Log.d(TAG, "connect failed! mIsSearchResult:"+mIsSearchResult+" threadId:"+this.getId());
				cancel();
				/*try{
						sleep(2000);
					}catch(Exception e1){
						e1.printStackTrace();
					}*/
					
				Log.d(TAG, "connect failed and delay to make it disconnected and reconnect"+"threadId:"+this.getId());
					if (mIsSearchResult) {// connect failed
						//cancelConnectThread();
						//it is result among search results ,so try to reconnect.
						onStateChange(BTState.DISCONNECTED, ErrFlag.CONNECT_FAIL_WITH_SEARCH);
					} else{//means it is a new device or not nearby 
	                    onStateChange(BTState.DISCONNECTED, ErrFlag.CONNECT_FAIL_WITHOUT_SEARCH);
					}
				return;
			}

			// Reset the ConnectThread because we're done
			synchronized (SppBluetoothChatService.this) {
				mConnectThread = null;
			}

			// Start the connected thread
			Log.d(TAG, "connect succeed!"+"threadId:"+this.getId());
			connected(mmSocket, mmDevice, mSocketType);
		}
		
		
		
		/*
		 Set<BluetoothDevice> pairedDevices = mAdapter.getBondedDevices();
					if (pairedDevices.size() > 0) {
						Log.d("unpairDevice", "unpairDevice start > 0");
						for (BluetoothDevice device : pairedDevices) {
							if (null != device.getName()&& device.getName().equals(LogicModel.DEFAULT_DEVICE_NAME)) {
								unpairDevice(device);
							}
						}
Log.d("unpairDevice", "unpairDevice begin");
				if (ThreeBtCtrl.getInstance().m_bConnectDeviceHavePaired) {
					
					Log.d("unpairDevice", "unpairDevice start");
					Set<BluetoothDevice> pairedDevices = mAdapter
							.getBondedDevices();
					if (pairedDevices.size() > 0) {

						Log.d("unpairDevice", "unpairDevice start > 0");
						
						for (BluetoothDevice device : pairedDevices) {
							if (null != device.getName()
									&& device.getName().equals(
											LogicModel.DEFAULT_DEVICE_NAME)) {

								unpairDevice(device);
							
							}
						}
					}
					
					ThreeBtCtrl.getInstance().m_bConnectDeviceHavePaired = false;
					ThreeBtCtrl.getInstance().doDiscovery();
				} else
					connectionFailed();
		 
		 
		 */

		public void cancel() {
			try {
				mmSocket.close();
			} catch (IOException e) {
				Log.e(TAG, "close() of connect " + mSocketType
						+ " socket failed", e);
			}
		}
	}

	
//	private void unpairDevice(BluetoothDevice device) {
//		Log.d("unpairDevice","unpairDevice : name = "+ device.getName() + " addr = "+ device.getAddress());
//		try {
//		    Method m = device.getClass()
//		        .getMethod("removeBond", (Class[]) null);
//		    m.invoke(device, (Object[]) null);
//		} catch (Exception e) {
//		    Log.e("unpairDevice", e.getMessage());
//		}
// }
	
	/**
	 * This thread runs during a connection with a remote device. It handles all
	 * incoming and outgoing transmissions.
	 */
	private class ConnectedThread extends Thread {
		private final BluetoothSocket mmSocket;
		private final InputStream mmInStream;
		private final OutputStream mmOutStream;
		private SppBluetoothChatService mChatService;
		public ConnectedThread(SppBluetoothChatService chatService,BluetoothSocket socket, String socketType) {
			mChatService = chatService;
			Log.d(TAG, "create ConnectedThread: " + socketType);
			mmSocket = socket;
			InputStream tmpIn = null;
			OutputStream tmpOut = null;

			// Get the BluetoothSocket input and output streams
			try {
				tmpIn = socket.getInputStream();
				tmpOut = socket.getOutputStream();
			} catch (IOException e) {
				Log.e(TAG, "temp sockets not created", e);
			}

			mmInStream = tmpIn;
			mmOutStream = tmpOut;
		}

		public void run() {
			Log.i(TAG, "BEGIN mConnectedThread");
			byte[] buffer = new byte[1024];
			int bytes;

			// Keep listening to the InputStream while connected
			while (true) {
				try {
					if (BTState.CONNECTED == mChatService.getState()) {
						// Read from the InputStream
						bytes = mmInStream.read(buffer);

						// Send the obtained bytes to the UI Activity
						// mHandler.obtainMessage(BluetoothChat.MESSAGE_READ,
						// bytes,
						// -1, buffer)
						// .sendToTarget();
						if(null != mOnChatServiceListener && bytes >0)
							Log.d("BT", "recv len:"+bytes);
							mOnChatServiceListener.onReceived(buffer,bytes);
					}

				} catch (IOException e) {
					Log.e(TAG, "read exception make it disconnected", e);
					onStateChange(BTState.DISCONNECTED,ErrFlag.READ_BUFFER_EXCEPTION);
					// Start the service over to restart listening mode
					//BluetoothChatService.this.start();
					break;
				}
			}
		}

		/**
		 * Write to the connected OutStream.
		 * 
		 * @param buffer
		 *            The bytes to write
		 */
		public void write(byte[] buffer) {
			try {
				if (BTState.CONNECTED == mChatService.getState()) {
					mmOutStream.write(buffer);
				}
				// Share the sent message back to the UI Activity
				// mHandler.obtainMessage(BluetoothChat.MESSAGE_WRITE, -1, -1,
				// buffer)
				// .sendToTarget();
			} catch (IOException e) {
				Log.e(TAG, "Exception during write", e);
			}
		}

		public void cancel() {
			try {
				mmSocket.close();
			} catch (IOException e) {
				Log.e(TAG, "close() of connect socket failed", e);
			}
		}
	}
}
